using GameFrame.Deterministic;
using System;
using System.Runtime.InteropServices;

namespace GameFrame
{
  /// <summary>
  /// The Transform2D is an entity component providing position and rotation a 2D object.
  /// </summary>
  [Serializable]
  [StructLayout(LayoutKind.Explicit)]
  public struct Transform2D : IComponent
  {
    public const int SIZE = 24;
    [FieldOffset(0)]
    public FPVector2 Position;
    [FieldOffset(16)]
    public FP Rotation;

    public static Transform2D Create(FPVector2 position = default (FPVector2), FP rotation = default (FP)) => new Transform2D()
    {
      Position = position,
      Rotation = rotation
    };

    public FPVector2 Right
    {
      get
      {
        FPVector2 right = new FPVector2();
        long sinRaw;
        long cosRaw;
        FPMath.SinCosRaw(this.Rotation, out sinRaw, out cosRaw);
        right.X.RawValue = cosRaw;
        right.Y.RawValue = sinRaw;
        return right;
      }
    }

    public FPVector2 Left
    {
      get
      {
        FPVector2 left = new FPVector2();
        long sinRaw;
        long cosRaw;
        FPMath.SinCosRaw(this.Rotation, out sinRaw, out cosRaw);
        left.X.RawValue = -cosRaw;
        left.Y.RawValue = -sinRaw;
        return left;
      }
    }

    public FPVector2 Up
    {
      get
      {
        FPVector2 up = new FPVector2();
        long sinRaw;
        long cosRaw;
        FPMath.SinCosRaw(this.Rotation, out sinRaw, out cosRaw);
        up.X.RawValue = -sinRaw;
        up.Y.RawValue = cosRaw;
        return up;
      }
    }

    public FPVector2 Down
    {
      get
      {
        FPVector2 down = new FPVector2();
        long sinRaw;
        long cosRaw;
        FPMath.SinCosRaw(this.Rotation, out sinRaw, out cosRaw);
        down.X.RawValue = sinRaw;
        down.Y.RawValue = -cosRaw;
        return down;
      }
    }

    public FPVector2 Forward => this.Right;

    public FPVector2 Back => this.Left;

    /// <summary>
    /// Transforms a <paramref name="point" /> from local to world space.
    /// See also: <seealso cref="M:Quantum.Transform2D.InverseTransformPoint(Photon.Deterministic.FPVector2)" />.
    /// </summary>
    /// <param name="point">A point in local space.</param>
    /// <returns>The transformed point in world space.</returns>
    public FPVector2 TransformPoint(FPVector2 point)
    {
      long sinRaw;
      long cosRaw;
      FPMath.SinCosRaw(this.Rotation, out sinRaw, out cosRaw);
      FPVector2 fpVector2;
      fpVector2.X.RawValue = this.Position.X.RawValue + ((point.X.RawValue * cosRaw >> 16) - (point.Y.RawValue * sinRaw >> 16));
      fpVector2.Y.RawValue = this.Position.Y.RawValue + ((point.X.RawValue * sinRaw >> 16) + (point.Y.RawValue * cosRaw >> 16));
      return fpVector2;
    }

    /// <summary>
    /// Transforms a <paramref name="point" /> from world to local space.
    /// See also: <seealso cref="M:Quantum.Transform2D.TransformPoint(Photon.Deterministic.FPVector2)" />.
    /// </summary>
    /// <param name="point">A point in world space.</param>
    /// <returns>The transformed point in local space.</returns>
    public FPVector2 InverseTransformPoint(FPVector2 point)
    {
      long sinRaw;
      long cosRaw;
      FPMath.SinCosRaw(this.Rotation, out sinRaw, out cosRaw);
      point.X.RawValue -= this.Position.X.RawValue;
      point.Y.RawValue -= this.Position.Y.RawValue;
      FPVector2 fpVector2;
      fpVector2.X.RawValue = (point.X.RawValue * cosRaw >> 16) + (point.Y.RawValue * sinRaw >> 16);
      fpVector2.Y.RawValue = (point.Y.RawValue * cosRaw >> 16) - (point.X.RawValue * sinRaw >> 16);
      return fpVector2;
    }

    /// <summary>
    /// Transforms a <paramref name="direction" /> from local to world space.
    /// See also: <seealso cref="M:Quantum.Transform2D.InverseTransformDirection(Photon.Deterministic.FPVector2)" />.
    /// </summary>
    /// <param name="direction">A direction in local space.</param>
    /// <returns>The transformed direction in world space.</returns>
    public FPVector2 TransformDirection(FPVector2 direction)
    {
      long sinRaw;
      long cosRaw;
      FPMath.SinCosRaw(this.Rotation, out sinRaw, out cosRaw);
      FPVector2 fpVector2;
      fpVector2.X.RawValue = (direction.X.RawValue * cosRaw >> 16) - (direction.Y.RawValue * sinRaw >> 16);
      fpVector2.Y.RawValue = (direction.X.RawValue * sinRaw >> 16) + (direction.Y.RawValue * cosRaw >> 16);
      return fpVector2;
    }

    /// <summary>
    /// Transforms a <paramref name="direction" /> from world to local space.
    /// See also: <seealso cref="M:Quantum.Transform2D.TransformDirection(Photon.Deterministic.FPVector2)" />.
    /// </summary>
    /// <param name="direction">A direction in world space.</param>
    /// <returns>The transformed direction in local space.</returns>
    public FPVector2 InverseTransformDirection(FPVector2 direction)
    {
      long sinRaw;
      long cosRaw;
      FPMath.SinCosRaw(this.Rotation, out sinRaw, out cosRaw);
      FPVector2 fpVector2;
      fpVector2.X.RawValue = (direction.X.RawValue * cosRaw >> 16) + (direction.Y.RawValue * sinRaw >> 16);
      fpVector2.Y.RawValue = (direction.Y.RawValue * cosRaw >> 16) - (direction.X.RawValue * sinRaw >> 16);
      return fpVector2;
    }

    public static unsafe void Serialize(void* ptr, FrameSerializer serializer)
    {
      Transform2D* transform2DPtr = (Transform2D*) ptr;
      FPVector2.Serialize((void*) &transform2DPtr->Position, (IDeterministicFrameSerializer) serializer);
      FP.Serialize((void*) &transform2DPtr->Rotation, (IDeterministicFrameSerializer) serializer);
    }

    public override int GetHashCode() => (17 * 31 + this.Position.GetHashCode()) * 31 + this.Rotation.GetHashCode();
  }
}